// Copyright (c) 2000 Just Objects B.V. <just@justobjects.nl>
// Distributable under LGPL license. See terms of license at gnu.org.

package nl.justobjects.pushlet.client;

import nl.justobjects.pushlet.core.Event;
import nl.justobjects.pushlet.core.XMLEventParser;

import java.io.*;
import java.net.URL;
import java.net.URLConnection;

/**
 * Client for Java HTTP client applets or apps.
 * <p>
 * Use this class within Java client applications or applets.
 * Implement a JavaPushletClientListener to receive callbacks for
 * Event objects pushed by the server. See DefaultJavaPushletClientListener and
 * main() below for an example.
 *
 * @version $Id: JavaPushletClient.java,v 1.9 2003/08/17 20:30:20 justb Exp $
 * @author Just van den Broecke - Just Objects &copy;
 *
 * @see JavaPushletClientListener
 * @see nl.justobjects.pushlet.test.PushletApplet
 */
public class JavaPushletClient implements Runnable {
	/** Reads from HTTP connection. */
	Reader reader;

	/** listener that gets called back on events. */
	private JavaPushletClientListener listener = null;

	/** Receiver thread. */
	private Thread thread = null;

	/** Default constructor. */
	public JavaPushletClient() {
	}

	/** Listen to events pushed from server for URL. */
	public void start(JavaPushletClientListener theListener, String anURL) {

 		// Stop any posible listening and thread
		stopListening();
		stopThread();

		// Listener to be called back on events from server.
		listener = theListener;

		// Open stream of serialized Event objects from server
		try {
			p("Connecting to " + anURL);
			URL url = new URL(anURL);
			URLConnection uc = url.openConnection();

			// Disable any kind of caching.
			uc.setUseCaches(false);
			uc.setDefaultUseCaches(false);

      		// Get the stream from the server.
			// reader = new BufferedReader(new InputStreamReader(uc.getInputStream()));
			// Note: somehow the client does not work with some JVMs when using
			// BufferedInputStream... So do unbuffered input.
			reader = new InputStreamReader(uc.getInputStream());

			// Print headers
			p("Transfer-Encoding=" + uc.getHeaderField("Transfer-Encoding"));
			p("Connection=" + uc.getHeaderField("Connection"));
			p("Connected");
		} catch (Exception e) {
			listener.onError("could not open stream to URL ex=" + e);
			return;
		}

		// All ok: start a receiver thread
		thread = new Thread(this);
		thread.start();
	}

	/** Receive event objects from server and callback listener. */
	public void run() {
		p("Start run()");

		// Get events while we're alive.
		while (thread != null && thread.isAlive()) {
			Event event = null;
			try {
				// p("Getting event...");
				// Get next event from server
				event = XMLEventParser.parse(reader);
				// p("Event received " + event);
			} catch (Throwable t) {

				// Stop and report error.
				p("Stop run() on exception e=" + t);
				if (listener != null) {
					listener.onError("exception during receive: " + t);
				}

				stopListening();
				return;
			}

			// Handle event by calling listener
			if (event != null && listener != null) {
				if (event.getSubject().equals("probe")) {
					listener.onProbe();
				} else {
					listener.onEvent(event);
				}
			}
		}
	}

	/** Stop listening; may restart later with start(). */
	public void stop() {
		p("In stop()");
		stopListening();
		stopThread();
	}

	/** Stop receiver thread. */
	public void stopThread() {
		p("In stopThread()");

		// Keep a reference such that we can kill it from here.
		Thread targetThread = thread;

		thread = null;

		// This should stop the main loop for this thread.
		// Killing a thread on a blcing read is tricky.
		// See also http://gee.cs.oswego.edu/dl/cpj/cancel.html
		if ((targetThread != null) && targetThread.isAlive()) {

			targetThread.interrupt();

			try {

				// Wait for it to die
				targetThread.join(500);
			}
			catch (InterruptedException ignore) {
			}

			// If current thread refuses to die,
			// take more rigorous methods.
			if (targetThread.isAlive()) {

				// Not preferred but may be needed
				// to stop during a blocking read.
				targetThread.stop();

				// Wait for it to die
				try {
					targetThread.join(500);
				}
				catch (InterruptedException ignore) {
				}
			}

			p("Stopped thread alive=" + targetThread.isAlive());

		}
	}

	/** Stop listening on stream from server. */
	public void stopListening() {
		p("In stopListening()");
		listener = null;

		if (reader != null) {

			try {
				reader.close();
				p("Closed reader ok");
			} catch (Exception ignore) {
			} finally {
				reader = null;
			}
		}
	}

	/** Util: print. */
	private void p(String s) {
		System.out.println("[PushletClient] " + s);
	}

	/** Test program for Java application listener. */
	public static void main(String args[]) {
		String url = "http://lombok:8080/pushlet/servlet/pushlet?format=xml&subject=/system";
		if (args.length == 1) {
			url = args[0];
		}
		new JavaPushletClient().start(new DefaultJavaPushletClientListener(), url);
	}
}

/** Class for testing purposes. */
class DefaultJavaPushletClientListener implements JavaPushletClientListener {
	/** Server probes the client; may use this for indicator. */
	synchronized public void onProbe() {
		p("probe");
	}

	/** Server has pushed event. */
	synchronized public void onEvent(Event theEvent) {
		p("event: \n" + theEvent);
	}

	/** Some error occurred. */
	synchronized public void onError(String message) {
		p("error msg=" + message);
	}

	/** Print util. */
	private void p(String s) {
		System.out.println(getClass().getName() + ":\n" + s);
	}
}


/*
 * $Log: JavaPushletClient.java,v $
 * Revision 1.9  2003/08/17 20:30:20  justb
 * cosmetic changes
 *
 * Revision 1.8  2003/08/15 08:37:40  justb
 * fix/add Copyright+LGPL file headers and footers
 *
 *
 */